﻿Option Explicit On
Imports System.Xml

Public Class CalcManager
    ' James D. Zoll
    ' 
    ' The CalcManager class acts as a flow controller for
    ' calculation of an optimal gearset from a provided set of criteria.
    ' There are several functions that take data that serve as setup, and then
    ' the doCalc procedure manages threads for both candidate checking and
    ' gemming of valid sets.  When complete it raises an event containing the best
    ' set.

    ' Variables that are from the calling object.
    ' The v character originally stood for valid / verified but is since somewhat
    ' deprecated.
    Private v_items(,) As Item
    Private v_numItems(0 To 12) As Integer
    Private v_maxNumItems As Integer
    Private v_gems() As Gem
    Private v_numGems As Integer
    Private v_weights As Weight
    Private v_race As playerRace
    Private v_profs(0 To 1) As Professions
    Private v_dwor2h As dwor2h
    Private v_forceTier(0 To 3) As Integer
    Private v_footEnchants() As Enchants
    Private v_numfEnch As Integer
    Private v_handEnchants() As Enchants
    Private v_numhEnch As Integer
    Private v_wristEnchants() As Enchants
    Private v_numwEnch As Integer
    Private v_expBonusRating(0 To 4) As Double
    Private v_expTalents As Integer
    Private v_forceHit As forceHitcap
    Private v_forceExp As Boolean

    ' Variables to help calculate the displayable values for the
    ' best set as returned from our gemming threads.
    Private sphitpercenttal As Integer
    Private arppercenttal As Integer
    Private nocspercenttal As Integer
    Private aura_melee As Boolean
    Private aura_spell As Boolean
    Private exptal As Integer
    Private numPieces As Double
    Private primarytree As String

    ' Variables holding values pulled from the constants.xml file
    ' containing values defined by the user.  'c' in this case stands
    ' for constant, but the variables themselves cannot be constants
    ' since they must be read at runtime.
    Private c_ArPCap As Double
    Private c_HitCap As Double
    Private c_HitCapPercent As Double
    Private c_SpellCap As Double
    Private c_SpellCapPercent As Double
    Private c_ExpCapRating As Double
    Private c_ExpCap As Double
    Private c_ArPperPercent As Double
    Private c_MeleeHitperPercent As Double
    Private c_SpellHitperPercent As Double
    Private c_ExpperExpertise As Double

    ' Variables to manage the threading of finding candidate gearsets.
    ' We must keep track of each thread, the object for that thread,
    ' and the number of threads done.  I use one object per thread since
    ' they must start at different indexes and the memory requirement
    ' is not that large.
    Private WithEvents gearFinder_0 As CandidateSetFinder
    Private WithEvents gearFinder_1 As CandidateSetFinder
    Private WithEvents gearFinder_2 As CandidateSetFinder
    Private WithEvents gearFinder_3 As CandidateSetFinder
    Private candidateSets() As CandidateSet
    Private numCandidateSets As Double
    Private maxCandidateIndex As Double
    Private numThreadsDone As Integer
    Private gearDone As Boolean

    ' Variables to manage the gemming threads.  Similar
    ' constructs are used to the gearing.
    Private WithEvents gemFinder_0 As GemSetFinder
    Private WithEvents gemFinder_1 As GemSetFinder
    Private WithEvents gemFinder_2 As GemSetFinder
    Private WithEvents gemFinder_3 As GemSetFinder
    Private gemsDone As Boolean
    Private bestGemSet As GemSet
    Private bestGemScore As Double
    Private bestGemStats(0 To 8) As Double

    ' The gemMatrix holds the four dimensional array
    ' on gem, hit, expertise, and socketcolor that is precalculated
    ' to determine what gems are valid for a socket under various conditions.
    Dim gemMatrix As GemValidityMatrix

    ' Generic ojbjects to hold thread locks for reporting
    ' events from our gearing and gemming threads.
    Private objLock As Object
    Private iterateLock As Object
    Private threadDoneLock As Object

    ' Variables here are public and can be accessed from the
    ' parent object.  They are iterated in events that are called
    ' from our gearing and gemming threads and keep track of
    ' how far along the process is so that a valid update can
    ' be maintained.
    Public candidatesChecked As Double
    Public candidatesToCheck As Double
    Public gemsChecked As Double
    Public gemsToCheck As Double
    Public candidatesDone As Boolean
    Public shouldCancel As Boolean
    Public progGemsDone As Boolean

    ' The single event exists to be called when doCalc and its
    ' subsequent procedures finish, letting the parent object pick it up and go from there.
    Public Event CalculateFinish(ByVal bestSet As FullSet)
    Public Event NoCandidateSets()
    Public Event NoGemSets()

    Enum forcehitcap As Integer
        none
        melee
        spell
    End Enum

    Enum stats
        str
        ap
        crit
        haste
        arp
        agi
        hit
        exp
        armor
    End Enum

    Enum itemIdx As Integer
        head = 0
        shoulder
        chest
        hand
        leg
        neck
        back
        wrist
        waist
        foot
        finger
        trinket
        weapon
    End Enum

    Enum slotIdx As Integer
        head = 0
        shoulder
        chest
        hand
        leg
        neck
        back
        wrist
        waist
        foot
        finger1
        finger2
        trinket1
        trinket2
        weapon1
        weapon2
    End Enum

    Enum playerRace As Integer
        Human
        Dwarf
        NightElf
        Gnome
        Draenei
        Orc
        Undead
        Tauren
        Troll
        BloodElf
    End Enum

    Enum dwor2h As Integer
        dualwield
        twohand
    End Enum

    Enum Professions As Integer
        None
        Alchemy
        Blacksmithing
        Jewelcrafting
        Enchanting
        Engineering
        Inscription
        Leatherworking
        Skinning
        Tailoring
    End Enum

    Enum Enchants As Integer
        none
        foot_Icewalker
        foot_GreaterAssault
        foot_NitroBoosts
        hand_Crusher
        hand_MajorStrength
        hand_Precision
        hand_HyperspeedAccelerators
        wrist_GreaterAssault
        wrist_Expertise
        wrist_FurLining
        head_ArcanumOfTorment
        chest_PowerfulStats
        leg_IcescaleArmor
        finger_Assault
        back_GreaterSpeed
        back_MajorAgility
        back_SwordguardEmbroidery
        shoulder_MastersAxe
        shoulder_GreaterAxe

    End Enum

    Public Sub New()
        ' Zero most everything out and define our object lock variables
        ' to have value.
        Try
            Dim loopvar As Integer
            For loopvar = 0 To 12
                v_numItems(loopvar) = 0
            Next
            v_numGems = 0
            v_maxNumItems = 0
            v_numfEnch = 0 : v_numhEnch = 0 : v_numwEnch = 0
            objLock = New Object
            threadDoneLock = New Object
            iterateLock = New Object
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub setPlayerRace(ByVal inRace As String)
        ' Get the race string from the main form and
        ' correctly set the enum type.
        Try
            Select Case inRace
                Case "Human"
                    v_race = playerRace.Human
                Case "Dwarf"
                    v_race = playerRace.Dwarf
                Case "Night Elf"
                    v_race = playerRace.NightElf
                Case "Gnome"
                    v_race = playerRace.Gnome
                Case "Draenei"
                    v_race = playerRace.Draenei
                Case "Orc"
                    v_race = playerRace.Orc
                Case "Undead"
                    v_race = playerRace.Undead
                Case "Tauren"
                    v_race = playerRace.Tauren
                Case "Troll"
                    v_race = playerRace.Troll
                Case "Blood Elf"
                    v_race = playerRace.BloodElf
            End Select
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub setPlayerProfs(ByVal prof1 As String, ByVal prof2 As String)
        ' Get the profession strings from the main form and correctly set
        ' the enum types.
        Try
            Dim tArray(0 To 1) As String
            tArray(0) = prof1 : tArray(1) = prof2

            Dim loopvar As Integer
            For loopvar = 0 To 1
                Select Case tArray(loopvar)
                    Case "None"
                        v_profs(loopvar) = Professions.None
                    Case "Alchemy"
                        v_profs(loopvar) = Professions.Alchemy
                    Case "Blacksmithing"
                        v_profs(loopvar) = Professions.Blacksmithing
                    Case "Jewelcrafting"
                        v_profs(loopvar) = Professions.Jewelcrafting
                    Case "Enchanting"
                        v_profs(loopvar) = Professions.Enchanting
                    Case "Engineering"
                        v_profs(loopvar) = Professions.Engineering
                    Case "Inscription"
                        v_profs(loopvar) = Professions.Inscription
                    Case "Leatherworking"
                        v_profs(loopvar) = Professions.Leatherworking
                    Case "Skinning"
                        v_profs(loopvar) = Professions.Skinning
                    Case "Tailoring"
                        v_profs(loopvar) = Professions.Tailoring
                End Select
            Next
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub setForceTier(ByVal t7 As Integer, ByVal t8 As Integer, ByVal t9 As Integer, ByVal t10 As Integer)
        ' Determine what Tier Set bonuses need to be forced, if any.
        ' Information is passed from the parent object.
        Try
            v_forceTier(0) = t7
            v_forceTier(1) = t8
            v_forceTier(2) = t9
            v_forceTier(3) = t10
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub setDWor2H(ByVal inString As String)
        ' Get the DW / 2H option from the main form so we
        ' know how to treat weapons.  Passed from parent.
        Try
            Select Case inString
                Case "Dual Wield"
                    v_dwor2h = dwor2h.dualwield
                Case "Two Handed"
                    v_dwor2h = dwor2h.twohand
            End Select
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub addGem(ByVal inGem As Gem)
        ' Add information about gems to the object.
        ' Gems are added one at a time as selected from the parent object.
        Try
            v_numGems += 1
            ReDim Preserve v_gems(0 To v_numGems - 1)
            v_gems(v_numGems - 1) = New Gem
            v_gems(v_numGems - 1) = inGem.clone
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub addItem(ByVal inItem As Item, ByVal slotString As String)
        ' Get information about valid items from the parent object.
        ' Items are added one at a time with a string representing
        ' what slot they should be in.
        Try
            Dim slotArray(0 To 13) As String
            Dim loopvar As Integer
            Dim slotIndex As Integer

            slotArray(0) = "head"
            slotArray(1) = "shoulder"
            slotArray(2) = "chest"
            slotArray(3) = "hand"
            slotArray(4) = "leg"
            slotArray(5) = "neck"
            slotArray(6) = "back"
            slotArray(7) = "wrist"
            slotArray(8) = "waist"
            slotArray(9) = "foot"
            slotArray(10) = "finger"
            slotArray(11) = "trinket"
            slotArray(12) = "twohand"
            slotArray(13) = "onehand"

            For loopvar = 0 To 13
                If slotArray(loopvar) = slotString Then
                    If loopvar < 12 Then
                        slotIndex = loopvar
                    Else
                        slotIndex = 12
                    End If
                End If
            Next

            v_numItems(slotIndex) += 1
            If v_numItems(slotIndex) > v_maxNumItems Then
                v_maxNumItems += 1
                ReDim Preserve v_items(0 To 12, 0 To v_maxNumItems - 1)
            End If
            v_items(slotIndex, v_numItems(slotIndex) - 1) = New Item
            v_items(slotIndex, v_numItems(slotIndex) - 1) = inItem.clone
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub addEnchant(ByVal inEnchant As Enchants)
        ' Add enchants as necessary as passed by the parent object.
        ' Enginnering enchants are added later as they are not selectable on the
        ' main form.
        Try
            Select Case inEnchant
                Case Enchants.foot_GreaterAssault, Enchants.foot_Icewalker, Enchants.foot_NitroBoosts
                    v_numfEnch += 1
                    ReDim Preserve v_footEnchants(0 To v_numfEnch - 1)
                    v_footEnchants(v_numfEnch - 1) = inEnchant
                Case Enchants.wrist_Expertise, Enchants.wrist_GreaterAssault
                    v_numwEnch += 1
                    ReDim Preserve v_wristEnchants(0 To v_numwEnch - 1)
                    v_wristEnchants(v_numwEnch - 1) = inEnchant
                Case Enchants.hand_Crusher, Enchants.hand_MajorStrength, Enchants.hand_Precision, Enchants.hand_HyperspeedAccelerators
                    v_numhEnch += 1
                    ReDim Preserve v_handEnchants(0 To v_numhEnch - 1)
                    v_handEnchants(v_numhEnch - 1) = inEnchant
            End Select
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub setWeights(ByVal str As Double, ByVal ap As Double, ByVal crit As Double, ByVal haste As Double, ByVal arp As Double, ByVal agi As Double, ByVal armor As Double, ByVal hit As Double, ByVal sphit As Double, ByVal exhit As Double, ByVal exp As Double, ByVal mhdps As Double, ByVal ohdps As Double, ByVal mhspeed As Double, ByVal ohspeed As Double, ByVal t7_2 As Double, ByVal t7_4 As Double, ByVal t8_2 As Double, ByVal t8_4 As Double, ByVal t9_2 As Double, ByVal t9_4 As Double, ByVal t10_2 As Double, ByVal t10_4 As Double)
        ' Get the weights from the calling object.  Nothing exciting here.
        Try
            v_weights = New Weight
            With v_weights
                .str = str
                .ap = ap
                .crit = crit
                .haste = haste
                .arp = arp
                .agi = agi
                .armor = armor
                .hit = hit
                .sphit = sphit
                .exhit = exhit
                .exp = exp
                .mhdps = mhdps
                .ohdps = ohdps
                .mhspeed = mhspeed
                .ohspeed = ohspeed
                .tier(0, 0) = t7_2
                .tier(0, 1) = t7_4
                .tier(1, 0) = t8_2
                .tier(1, 1) = t8_4
                .tier(2, 0) = t9_2
                .tier(2, 1) = t9_4
                .tier(3, 0) = t10_2
                .tier(3, 1) = t10_4
            End With
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub finalVars(ByVal spHitPercentTalIN As Integer, ByVal expTalIN As Integer, ByVal arpPercentTalIN As Integer, ByVal NoCSPercentTalIN As Integer, ByVal aura_MeleeIN As Boolean, ByVal aura_SpellIN As Boolean, ByVal forceHitIN As forcehitcap, ByVal forceExpIN As Boolean, ByVal PrimaryTreeIN As String)
        ' These are the remaining miscellaneous variables that we need to do our calculation.
        ' They are passed from the parent object and set to internal private variables.
        Try
            sphitpercenttal = spHitPercentTalIN
            exptal = expTalIN
            arppercenttal = arpPercentTalIN
            nocspercenttal = NoCSPercentTalIN
            aura_melee = aura_MeleeIN
            aura_spell = aura_SpellIN
            v_expTalents = expTalIN
            v_forceHit = forceHitIN
            v_forceExp = forceExpIN
            PrimaryTree = PrimaryTreeIN
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub doCalc()
        ' Do calc is called when we wish to find the best set.  It will terminate by
        ' calling the CalcFinished() event to let the parent object know
        ' that we are done.
        Try

            ' Generic looping variables.
            Dim loopvar As Integer
            Dim loopvar2 As Integer

            ' Set our progress variables to show that we are just beginning.
            candidatesChecked = 0
            candidatesToCheck = 0
            candidatesDone = False
            gemsChecked = 0
            gemsToCheck = 0
            progGemsDone = False

            ' Load constants from constants.xml and read the values
            ' so that we can accurately rate hit, expertise, and ArP.
            Dim m_xmld As XmlDocument
            Dim m_nodelist As XmlNodeList
            Dim m_node As XmlNode

            m_xmld = New XmlDocument()
            m_xmld.Load(Windows.Forms.Application.CommonAppDataPath & "\data_constants.xml")
            m_nodelist = m_xmld.SelectNodes("/constants")
            m_node = m_nodelist(0)
            c_MeleeHitperPercent = Double.Parse(m_node.ChildNodes.Item(0).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            c_SpellHitperPercent = Double.Parse(m_node.ChildNodes.Item(1).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            c_HitCapPercent = Double.Parse(m_node.ChildNodes.Item(2).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            c_SpellCapPercent = Double.Parse(m_node.ChildNodes.Item(3).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            c_ExpperExpertise = Double.Parse(m_node.ChildNodes.Item(4).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            c_ExpCap = Double.Parse(m_node.ChildNodes.Item(5).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            c_ArPperPercent = Double.Parse(m_node.ChildNodes.Item(6).InnerText, System.Globalization.CultureInfo.InvariantCulture)

            ' Take the constants that we've loaded and determine the various
            ' rating caps for our cappable stats.
            Dim tempint As Integer
            c_ArPCap = (100 - arppercenttal) * c_ArPperPercent
            c_ExpCapRating = (c_ExpCap - exptal) * c_ExpperExpertise
            If aura_melee = True Then
                tempint = 1
            Else
                tempint = 0
            End If
            tempint += nocspercenttal
            c_HitCap = (c_HitCapPercent - tempint) * c_MeleeHitperPercent
            If aura_spell = True Then
                tempint = 4 ' 3 from misery
            Else
                tempint = 3
            End If
            tempint += sphitpercenttal
            c_SpellCap = (c_SpellCapPercent - tempint) * c_SpellHitperPercent

            ' Determine the number of pieces based on the DW or 2H flag.
            If v_dwor2h = dwor2h.dualwield Then
                numPieces = 16
            Else
                numPieces = 15
            End If

            ' Loop through and add a prismatic socket to all belts.  Additionally add
            ' a prismatic socket to gloves and wrists if one of the selected professions
            ' is blacksmithing.
            For loopvar = 0 To v_numItems(itemIdx.waist) - 1
                v_items(itemIdx.waist, loopvar).addSocket(Item.colors.prismatic)
            Next

            If v_profs(0) = Professions.Blacksmithing Or v_profs(1) = Professions.Blacksmithing Then
                For loopvar = 0 To v_numItems(itemIdx.wrist) - 1
                    v_items(itemIdx.wrist, loopvar).addSocket(Item.colors.prismatic)
                Next
                For loopvar = 0 To v_numItems(itemIdx.hand) - 1
                    v_items(itemIdx.hand, loopvar).addSocket(Item.colors.prismatic)
                Next
            End If

            ' Prior to optimization of enchants, add both Enginnering enchants to our arrays of
            ' possible enchants.  Leatherworking enchant must also be added to bracers if
            ' applicable. If the Fur Lining is added, delete Greater Assault if it is present.
            Dim GAFound As Boolean
            If v_profs(0) = Professions.Engineering Or v_profs(1) = Professions.Engineering Then
                addEnchant(Enchants.hand_HyperspeedAccelerators)
                addEnchant(Enchants.foot_NitroBoosts)
            End If
            If v_profs(0) = Professions.Leatherworking Or v_profs(1) = Professions.Leatherworking Then
                GAFound = False
                For loopvar = 0 To v_numwEnch - 1
                    If v_wristEnchants(loopvar) = Enchants.wrist_GreaterAssault Then
                        v_wristEnchants(loopvar) = Enchants.wrist_FurLining
                        GAFound = True
                    End If
                Next
                If GAFound = False Then
                    addEnchant(Enchants.wrist_FurLining)
                End If
            End If

            ' Enchant optimization.  For bracers we can remove the expertise enchant if it has less
            ' value than greater assault / fur linging at full weight and both have been selected.  
            'If only one bracer enchant was chosen, we cannot remove it.
            If v_numwEnch > 1 Then
                If v_profs(0) = Professions.Leatherworking Or v_profs(1) = Professions.Leatherworking Then
                    If 130 * v_weights.ap > 15 * v_weights.exp Then
                        ReDim v_wristEnchants(0 To 0)
                        v_wristEnchants(0) = Enchants.wrist_FurLining
                        v_numwEnch = 1
                    End If
                Else
                    If 50 * v_weights.ap > 15 * v_weights.exp Then
                        ReDim v_wristEnchants(0 To 0)
                        v_wristEnchants(0) = Enchants.wrist_GreaterAssault
                        v_numwEnch = 1
                    End If
                End If
            End If

            ' Enchant optimization.  For gloves we can remove any enchant such that its best value
            ' is worse than another enchants worst value.  For example, if hit past both caps had a weight
            ' such that precision was better than greater assault at that point, greater assault will be 
            ' removed.  More likely is precision being removed and Greater Assault or Major Strength being
            ' cut to the better of the two.
            Dim curEnchMax As Double, curEnchMin As Double

            For loopvar = 0 To v_numhEnch - 1
                Select Case v_handEnchants(loopvar)
                    Case Enchants.hand_Crusher
                        curEnchMax = 44 * v_weights.ap
                    Case Enchants.hand_HyperspeedAccelerators
                        curEnchMax = (340 * v_weights.haste) * (12 / 60)
                    Case Enchants.hand_MajorStrength
                        curEnchMax = 15 * v_weights.str
                    Case Enchants.hand_Precision
                        curEnchMax = 20 * v_weights.hit
                    Case Enchants.none
                        curEnchMax = 0
                End Select
                For loopvar2 = 0 To v_numhEnch - 1
                    If loopvar <> loopvar2 Then
                        Select Case v_handEnchants(loopvar2)
                            Case Enchants.hand_Crusher
                                curEnchMin = 44 * v_weights.ap
                            Case Enchants.hand_HyperspeedAccelerators
                                curEnchMin = (340 * v_weights.haste) * (12 / 60)
                            Case Enchants.hand_MajorStrength
                                curEnchMin = 15 * v_weights.str
                            Case Enchants.hand_Precision
                                curEnchMin = 20 * v_weights.exhit
                            Case Enchants.none
                                curEnchMin = 0
                        End Select
                        If curEnchMax < curEnchMin Then
                            v_handEnchants(loopvar) = Enchants.none
                        End If
                    End If
                Next
            Next
            loopvar = 0
            Do While loopvar < v_numhEnch
                If v_handEnchants(loopvar) = Enchants.none Then
                    For loopvar2 = loopvar To v_numhEnch - 2
                        v_handEnchants(loopvar2) = v_handEnchants(loopvar2 + 1)
                    Next
                    v_numhEnch -= 1
                    ReDim Preserve v_handEnchants(0 To v_numhEnch - 1)
                End If
                loopvar += 1
            Loop

            ' Enchant optimization.  Optimization for boot enchants is identical to that
            ' of the hand enchants.
            For loopvar = 0 To v_numfEnch - 1
                Select Case v_footEnchants(loopvar)
                    Case Enchants.foot_GreaterAssault
                        curEnchMax = 32 * v_weights.ap
                    Case Enchants.foot_Icewalker
                        curEnchMax = (12 * v_weights.hit) + (12 * v_weights.crit)
                    Case Enchants.foot_NitroBoosts
                        curEnchMax = (24 * v_weights.crit)
                    Case Enchants.none
                        curEnchMax = 0
                End Select
                For loopvar2 = 0 To v_numfEnch - 1
                    If loopvar <> loopvar2 Then
                        Select Case v_footEnchants(loopvar2)
                            Case Enchants.foot_GreaterAssault
                                curEnchMin = 32 * v_weights.ap
                            Case Enchants.foot_Icewalker
                                curEnchMin = (12 * v_weights.exhit) + (12 * v_weights.crit)
                            Case Enchants.foot_NitroBoosts
                                curEnchMin = (24 * v_weights.crit)
                            Case Enchants.none
                                curEnchMin = 0
                        End Select
                        If curEnchMax < curEnchMin Then
                            v_footEnchants(loopvar) = Enchants.none
                        End If
                    End If
                Next
            Next
            loopvar = 0
            Do While loopvar < v_numfEnch
                If v_footEnchants(loopvar) = Enchants.none Then
                    For loopvar2 = loopvar To v_numfEnch - 2
                        v_footEnchants(loopvar2) = v_footEnchants(loopvar2 + 1)
                    Next
                    v_numfEnch -= 1
                    ReDim Preserve v_footEnchants(0 To v_numfEnch - 1)
                End If
                loopvar += 1
            Loop

            ' Gem matrix setup.  The goal is to determine what gems from all of the
            ' chosen gems are valid to attempt to put it what color sockets while gemming
            ' and with what combinations of hit and expertise rating on the set.
            gemMatrix = New GemValidityMatrix
            gemMatrix.setupMatrix(v_numGems, c_SpellCap, c_ExpCapRating, c_HitCap, v_forceHit, v_forceExp)
            gemMatrix.determineOptimal(v_gems, v_weights)

            ' Expertise racial bonuses.  We build this array so that we can
            ' compare weapon types against it to add additional expertise rating where
            ' necessary.
            For loopvar = 0 To 4
                v_expBonusRating(loopvar) = 0
            Next
            If v_race = playerRace.Orc Then
                v_expBonusRating(Item.weptypes.axe) = 5 * c_ExpperExpertise
            End If
            If v_race = playerRace.Human Then
                v_expBonusRating(Item.weptypes.mace) = 3 * c_ExpperExpertise
                v_expBonusRating(Item.weptypes.sword) = 3 * c_ExpperExpertise
            End If
            If v_race = playerRace.Dwarf Then
                v_expBonusRating(Item.weptypes.mace) = 5 * c_ExpperExpertise
            End If

            ' Item optimization and trimming.  Iterate through all items and determine if there
            ' are any items for which the max value (including max value sockets) is less than the
            ' minimum value (including best non-cap stat sockets) of another item.  these items can 
            ' be eliminated.  Trimming will not happen for rings, trinkets, or weapons.
            Dim bestSocket As Double = 0
            Dim bestNonCapSocket As Double = 0
            Dim minItemScores() As Double
            Dim bestMinItemScore As Double
            Dim maxItemScores() As Double
            Dim curItemScore As Double
            Dim tempItemArray() As Item
            Dim tempNumItems As Integer

            bestSocket = 20 * System.Math.Max(v_weights.agi, System.Math.Max(v_weights.ap, System.Math.Max(v_weights.arp, System.Math.Max(v_weights.crit, System.Math.Max(v_weights.exp, System.Math.Max(v_weights.haste, System.Math.Max(v_weights.hit, v_weights.str)))))))
            bestNonCapSocket = 20 * System.Math.Max(v_weights.agi, System.Math.Max(v_weights.ap, System.Math.Max(v_weights.arp, System.Math.Max(v_weights.crit, System.Math.Max(v_weights.haste, v_weights.str)))))

            For loopvar = 0 To 9
                ReDim minItemScores(0 To v_numItems(loopvar) - 1)
                ReDim maxItemScores(0 To v_numItems(loopvar) - 1)
                For loopvar2 = 0 To v_numItems(loopvar) - 1
                    curItemScore = 0
                    curItemScore += (v_weights.agi * v_items(loopvar, loopvar2).agi)
                    curItemScore += (v_weights.ap * v_items(loopvar, loopvar2).ap)
                    curItemScore += (v_weights.armor * v_items(loopvar, loopvar2).armor)
                    curItemScore += (v_weights.arp * v_items(loopvar, loopvar2).arp)
                    curItemScore += (v_weights.crit * v_items(loopvar, loopvar2).crit)
                    curItemScore += (v_weights.haste * v_items(loopvar, loopvar2).haste)
                    curItemScore += (v_weights.str * v_items(loopvar, loopvar2).str)

                    minItemScores(loopvar2) = curItemScore + (v_weights.exhit * v_items(loopvar, loopvar2).hit) + (bestNonCapSocket * v_items(loopvar, loopvar2).numSockets)
                    maxItemScores(loopvar2) = curItemScore + (v_weights.hit * v_items(loopvar, loopvar2).hit) + (v_weights.exp * v_items(loopvar, loopvar2).exp) + (bestSocket * v_items(loopvar, loopvar2).numSockets)
                    With v_items(loopvar, loopvar2)
                        Select Case v_items(loopvar, loopvar2).socketstat
                            Case Item.stats.agi
                                maxItemScores(loopvar2) += (v_weights.agi * .agi)
                            Case Item.stats.ap
                                maxItemScores(loopvar2) += (v_weights.ap * .ap)
                            Case Item.stats.arp
                                maxItemScores(loopvar2) += (v_weights.arp * .arp)
                            Case Item.stats.crit
                                maxItemScores(loopvar2) += (v_weights.crit * .crit)
                            Case Item.stats.exp
                                maxItemScores(loopvar2) += (v_weights.exp * .exp)
                            Case Item.stats.haste
                                maxItemScores(loopvar2) += (v_weights.haste * .haste)
                            Case Item.stats.hit
                                maxItemScores(loopvar2) += (v_weights.hit * .hit)
                            Case Item.stats.str
                                maxItemScores(loopvar2) += (v_weights.str * .str)
                        End Select
                    End With
                Next
                bestMinItemScore = 0
                For loopvar2 = 0 To v_numItems(loopvar) - 1
                    If minItemScores(loopvar2) > bestMinItemScore Then
                        bestMinItemScore = minItemScores(loopvar2)
                    End If
                Next
                ReDim tempItemArray(0 To 0)
                tempNumItems = 0
                For loopvar2 = 0 To v_numItems(loopvar) - 1
                    If maxItemScores(loopvar2) >= bestMinItemScore Or InStr(v_items(loopvar, loopvar2).name, "Koltira's") <> 0 Or InStr(v_items(loopvar, loopvar2).name, "Darkruned") <> 0 Or InStr(v_items(loopvar, loopvar2).name, "Valorous") <> 0 Then
                        tempNumItems += 1
                        ReDim Preserve tempItemArray(0 To tempNumItems - 1)
                        tempItemArray(tempNumItems - 1) = New Item
                        tempItemArray(tempNumItems - 1) = v_items(loopvar, loopvar2).clone
                    End If
                Next
                v_numItems(loopvar) = tempNumItems
                For loopvar2 = 0 To v_maxNumItems - 1
                    If loopvar2 <= v_numItems(loopvar) - 1 Then
                        v_items(loopvar, loopvar2) = tempItemArray(loopvar2).clone
                    Else
                        v_items(loopvar, loopvar2) = Nothing
                    End If
                Next
            Next

            ' Setup is now complete and all variables have been calculated.  We now need to
            ' determine the number of permutations of gear so that we can accurately judge the start
            ' and end points for each of the gearing threads.
            Dim numPerms(0 To 12) As Integer

            For loopvar = 0 To 9
                numPerms(loopvar) = v_numItems(loopvar)
            Next
            tempint = 0
            For loopvar = 0 To v_numItems(10) - 1
                If v_items(itemIdx.finger, loopvar).unique = False Then
                    tempint += 1
                End If
                For loopvar2 = loopvar + 1 To v_numItems(10) - 1
                    tempint += 1
                Next
            Next
            numPerms(itemIdx.finger) = tempint
            tempint = 0
            For loopvar = 0 To v_numItems(itemIdx.trinket) - 1
                For loopvar2 = loopvar + 1 To v_numItems(itemIdx.trinket) - 1
                    tempint += 1
                Next
            Next
            numPerms(itemIdx.trinket) = tempint
            If v_dwor2h = dwor2h.twohand Then
                numPerms(itemIdx.weapon) = v_numItems(itemIdx.weapon)
            Else
                tempint = 0
                For loopvar = 0 To v_numItems(itemIdx.weapon) - 1
                    For loopvar2 = loopvar To v_numItems(itemIdx.weapon) - 1
                        If loopvar = loopvar2 Then
                            If v_items(itemIdx.weapon, loopvar).wephand = Item.wephands.onehand And v_items(itemIdx.weapon, loopvar).unique = False Then
                                tempint += 1
                            End If
                        Else
                            If v_items(itemIdx.weapon, loopvar).wephand = Item.wephands.onehand Or v_items(itemIdx.weapon, loopvar2).wephand = Item.wephands.onehand Then
                                tempint += 1
                                If v_items(itemIdx.weapon, loopvar).wephand = Item.wephands.onehand And v_items(itemIdx.weapon, loopvar2).wephand = Item.wephands.onehand Then
                                    'If both are one hand then two possible combos since either can be in both hand.
                                    tempint += 1
                                End If
                            Else
                                If v_items(itemIdx.weapon, loopvar).wephand = Item.wephands.mainhand And v_items(itemIdx.weapon, loopvar2).wephand = Item.wephands.offhand Then
                                    tempint += 1
                                ElseIf v_items(itemIdx.weapon, loopvar).wephand = Item.wephands.offhand And v_items(itemIdx.weapon, loopvar2).wephand = Item.wephands.mainhand Then
                                    tempint += 1
                                End If
                            End If
                        End If
                    Next
                Next
                numPerms(itemIdx.weapon) = tempint
            End If

            ' Given the number of permutations in each slot, it is trivial to determine the total
            ' complexity of the calculation and assign equal lengths of IDs to process for each thread.
            Dim totalComplexity As Double
            Dim threadSize As Double
            Dim sizeDiff As Double
            Dim threadStart(0 To 3) As Double
            Dim threadEnd(0 To 3) As Double

            totalComplexity = 1
            For loopvar = 0 To 12
                totalComplexity *= numPerms(loopvar)
            Next
            candidatesToCheck = totalComplexity
            threadSize = Int(totalComplexity / 4)
            sizeDiff = totalComplexity - (threadSize * 4)
            For loopvar = 0 To 3
                threadStart(loopvar) = loopvar * threadSize
                threadEnd(loopvar) = ((loopvar + 1) * threadSize) - 1
            Next
            threadEnd(3) = totalComplexity

            ' Now we must create the four objects and four threads, send them the appropriate data
            ' as collected previously.
            Dim myThread_0 As System.Threading.Thread
            Dim myThread_1 As System.Threading.Thread
            Dim myThread_2 As System.Threading.Thread
            Dim myThread_3 As System.Threading.Thread

            gearFinder_0 = New CandidateSetFinder
            gearFinder_1 = New CandidateSetFinder
            gearFinder_2 = New CandidateSetFinder
            gearFinder_3 = New CandidateSetFinder
            gearFinder_0.setData(v_items, v_numItems, v_maxNumItems, v_weights, v_race, v_dwor2h, v_forceTier, v_footEnchants, v_wristEnchants, v_handEnchants, v_numfEnch, v_numwEnch, v_numhEnch, v_expBonusRating, c_ArPCap, c_HitCap, c_SpellCap, c_ExpCapRating, v_forceHit, v_forceExp, primarytree)
            gearFinder_0.setStart(threadStart(0), threadEnd(0), numPerms)
            myThread_0 = New System.Threading.Thread(AddressOf gearFinder_0.findSets)
            gearFinder_1.setData(v_items, v_numItems, v_maxNumItems, v_weights, v_race, v_dwor2h, v_forceTier, v_footEnchants, v_wristEnchants, v_handEnchants, v_numfEnch, v_numwEnch, v_numhEnch, v_expBonusRating, c_ArPCap, c_HitCap, c_SpellCap, c_ExpCapRating, v_forceHit, v_forceExp, primarytree)
            gearFinder_1.setStart(threadStart(1), threadEnd(1), numPerms)
            myThread_1 = New System.Threading.Thread(AddressOf gearFinder_1.findSets)
            gearFinder_2.setData(v_items, v_numItems, v_maxNumItems, v_weights, v_race, v_dwor2h, v_forceTier, v_footEnchants, v_wristEnchants, v_handEnchants, v_numfEnch, v_numwEnch, v_numhEnch, v_expBonusRating, c_ArPCap, c_HitCap, c_SpellCap, c_ExpCapRating, v_forceHit, v_forceExp, primarytree)
            gearFinder_2.setStart(threadStart(2), threadEnd(2), numPerms)
            myThread_2 = New System.Threading.Thread(AddressOf gearFinder_2.findSets)
            gearFinder_3.setData(v_items, v_numItems, v_maxNumItems, v_weights, v_race, v_dwor2h, v_forceTier, v_footEnchants, v_wristEnchants, v_handEnchants, v_numfEnch, v_numwEnch, v_numhEnch, v_expBonusRating, c_ArPCap, c_HitCap, c_SpellCap, c_ExpCapRating, v_forceHit, v_forceExp, primarytree)
            gearFinder_3.setStart(threadStart(3), threadEnd(3), numPerms)
            myThread_3 = New System.Threading.Thread(AddressOf gearFinder_3.findSets)

            ' Brief variable setup for our control variables to ready us to start the threads.
            numCandidateSets = 0
            ReDim candidateSets(0 To 999)
            maxCandidateIndex = 999
            numThreadsDone = 0
            gearDone = False

            ' Start the four candidate gear finder threads.  When this is complete,
            ' loop indefinitely while they run checking for a cancel signal 5 times per
            ' second.
            myThread_0.Start()
            myThread_1.Start()
            myThread_2.Start()
            myThread_3.Start()
            Do
                System.Threading.Thread.Sleep(200)
                If shouldCancel = True Then
                    myThread_0.Abort()
                    myThread_1.Abort()
                    myThread_2.Abort()
                    myThread_3.Abort()
                    myThread_0 = Nothing
                    myThread_1 = Nothing
                    myThread_2 = Nothing
                    myThread_3 = Nothing
                    Exit Sub
                End If
            Loop While gearDone = False

            ' Each gear candidate thread has finished.  At this point we trim the candidate
            ' sets to reduce the number that need to ge gemmed.  We that is complete, move to the gemming
            ' procedure.
            If numCandidateSets = 0 Then
                RaiseEvent NoCandidateSets()
                Exit Sub
            End If
            trimCandidates()
            doGems()
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try

    End Sub

    Private Sub gearFinder_AddCandidateSet(ByVal itemIDs() As Integer, ByVal minScore As Double, ByVal maxScore As Double, ByVal wEnch As Enchants, ByVal hEnch As Enchants, ByVal fEnch As Enchants) Handles gearFinder_0.AddCandidateSet, gearFinder_1.AddCandidateSet, gearFinder_2.AddCandidateSet, gearFinder_3.AddCandidateSet
        ' This is the procedure for the event fired when a gearing procedure finds a candidate set
        ' that it believes is valid.  Allocation happens in blocks because the ReDim procedure is
        ' time intensive.
        Try
            SyncLock objLock
                numCandidateSets += 1
                If numCandidateSets - 1 > maxCandidateIndex Then
                    maxCandidateIndex += 1000
                    ReDim Preserve candidateSets(0 To maxCandidateIndex)
                End If
                candidateSets(numCandidateSets - 1) = New CandidateSet(numPieces)
                candidateSets(numCandidateSets - 1).itemIDs = itemIDs.Clone
                candidateSets(numCandidateSets - 1).minScore = minScore
                candidateSets(numCandidateSets - 1).maxScore = maxScore
                candidateSets(numCandidateSets - 1).fEnch = fEnch
                candidateSets(numCandidateSets - 1).hEnch = hEnch
                candidateSets(numCandidateSets - 1).wEnch = wEnch
            End SyncLock
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Private Sub gearFinder_calcFinished() Handles gearFinder_0.calcFinished, gearFinder_1.calcFinished, gearFinder_2.calcFinished, gearFinder_3.calcFinished
        ' This handles the event fired when a gearing thread finishes.  Unless it is the final thread,
        ' only a variable is incremented.  For the final thread, a boolean flag is set which will allow
        ' the main thread to continue to gemming.
        Try
            SyncLock threadDoneLock
                numThreadsDone += 1
                If numThreadsDone = 4 Then
                    gearDone = True
                End If
            End SyncLock
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Private Sub trimCandidates()
        ' The purpose of this is to reduce the number of candidate sets
        ' that must be gemmed.  All sets with a maximum score that is less
        ' than the best minimum score over all sets are eliminated and the
        ' array is resized and rebounded appropriately.
        Dim bestMinScore As Double
        Dim loopvar As Double
        Dim curOffset As Double

        Try
            bestMinScore = 0
            For loopvar = 0 To numCandidateSets - 1
                If candidateSets(loopvar).minScore > bestMinScore Then
                    bestMinScore = candidateSets(loopvar).minScore
                End If
            Next
            curOffset = 0
            For loopvar = 0 To numCandidateSets - 1
                If candidateSets(loopvar).maxScore < bestMinScore Then
                    curOffset += 1
                Else
                    candidateSets(loopvar - curOffset) = candidateSets(loopvar)
                End If
            Next
            numCandidateSets -= curOffset
            ReDim Preserve candidateSets(0 To numCandidateSets - 1)
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try

    End Sub

    Private Sub doGems()
        ' This function follows doCalc in the main flow of CalcManager.
        ' At this point in time candidate sets have been found and it is time
        ' to set up and gemming objects and threads and run them.
        Try
            ' General variable declaration.
            Dim loopvar As Integer
            Dim threadStart(0 To 3) As Double
            Dim threadEnd(0 To 3) As Double

            ' Public variables are updated to keep the progress accessible from outside the object
            ' up to date.
            candidatesChecked = candidatesToCheck
            candidatesDone = True
            gemsToCheck = numCandidateSets

            ' To reduce memory consumption we will clean up the gearFinder objects
            ' used to find candidate sets.
            gearFinder_0 = Nothing
            gearFinder_1 = Nothing
            gearFinder_2 = Nothing
            gearFinder_3 = Nothing

            ' Now declare and instantiate the four threads and objects that are used to iteratively 
            ' find the best set after gemming.  This is quite similar to how the gearFinder objects
            ' and threads were used.  The threads are also bounded in a similar manner.
            Dim GemThread_0 As System.Threading.Thread
            Dim GemThread_1 As System.Threading.Thread
            Dim GemThread_2 As System.Threading.Thread
            Dim GemThread_3 As System.Threading.Thread
            Dim threadSize As Double
            Dim sizeDiff As Double

            gemFinder_0 = New GemSetFinder
            gemFinder_1 = New GemSetFinder
            gemFinder_2 = New GemSetFinder
            gemFinder_3 = New GemSetFinder
            threadSize = System.Math.Floor(numCandidateSets / 4)
            sizeDiff = numCandidateSets - (threadSize * 4)
            For loopvar = 0 To 3
                threadStart(loopvar) = loopvar * threadSize
                threadEnd(loopvar) = ((loopvar + 1) * threadSize) - 1
            Next
            threadEnd(3) = numCandidateSets - 1
            gemFinder_0.setData(v_items, v_numItems, v_weights, v_race, v_dwor2h, v_expBonusRating, c_ArPCap, c_HitCap, c_SpellCap, c_ExpCapRating, gemMatrix, v_numGems, v_gems, numCandidateSets, candidateSets, v_forceHit, v_forceExp, primarytree)
            gemFinder_0.setStart(threadStart(0), threadEnd(0))
            GemThread_0 = New System.Threading.Thread(AddressOf gemFinder_0.findSet)
            gemFinder_1.setData(v_items, v_numItems, v_weights, v_race, v_dwor2h, v_expBonusRating, c_ArPCap, c_HitCap, c_SpellCap, c_ExpCapRating, gemMatrix, v_numGems, v_gems, numCandidateSets, candidateSets, v_forceHit, v_forceExp, primarytree)
            gemFinder_1.setStart(threadStart(1), threadEnd(1))
            GemThread_1 = New System.Threading.Thread(AddressOf gemFinder_1.findSet)
            gemFinder_2.setData(v_items, v_numItems, v_weights, v_race, v_dwor2h, v_expBonusRating, c_ArPCap, c_HitCap, c_SpellCap, c_ExpCapRating, gemMatrix, v_numGems, v_gems, numCandidateSets, candidateSets, v_forceHit, v_forceExp, primarytree)
            gemFinder_2.setStart(threadStart(2), threadEnd(2))
            GemThread_2 = New System.Threading.Thread(AddressOf gemFinder_2.findSet)
            gemFinder_3.setData(v_items, v_numItems, v_weights, v_race, v_dwor2h, v_expBonusRating, c_ArPCap, c_HitCap, c_SpellCap, c_ExpCapRating, gemMatrix, v_numGems, v_gems, numCandidateSets, candidateSets, v_forceHit, v_forceExp, primarytree)
            gemFinder_3.setStart(threadStart(3), threadEnd(3))
            GemThread_3 = New System.Threading.Thread(AddressOf gemFinder_3.findSet)

            ' Setting these variables verifies that our progress will be handled correctly
            ' by the threading events.
            numThreadsDone = 0
            bestGemScore = 0
            gemsDone = False

            ' Start the threads.  When they have started, loop indefinitely until the gemsDone flag
            ' flips to true, all the while checking for a cancel signal 5 times per second.
            GemThread_0.Start()
            GemThread_1.Start()
            GemThread_2.Start()
            GemThread_3.Start()
            Do
                System.Threading.Thread.Sleep(200)
                If shouldCancel = True Then
                    GemThread_0.Abort()
                    GemThread_1.Abort()
                    GemThread_2.Abort()
                    GemThread_3.Abort()
                    GemThread_0 = Nothing
                    GemThread_1 = Nothing
                    GemThread_2 = Nothing
                    GemThread_3 = Nothing
                    Exit Sub
                End If
            Loop While gemsDone = False

            ' The threads are complete.  Dispose them so that we can recapture
            ' the memory used.
            GemThread_0 = Nothing
            GemThread_1 = Nothing
            GemThread_2 = Nothing
            GemThread_3 = Nothing

            ' If no set was found, raise the event and exit.
            If bestGemScore = 0 Then
                RaiseEvent NoGemSets()
                Exit Sub
            End If

            ' The finishCalculate procedure will set up the best set variable that will
            ' be passed back to the parent object, and then raise the event.
            ' This is the end of execution for CalcManager
            finishCalculate()
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try

    End Sub

    Private Sub gemFinder_calcFinished(ByVal bestSet As GemSet, ByVal bestStats() As Double, ByVal bestScore As Double) Handles gemFinder_0.calcFinished, gemFinder_1.calcFinished, gemFinder_2.calcFinished, gemFinder_3.calcFinished
        ' This procedure handles the event fired by a gemming thread when it finishes.  It contains
        ' arguments with the best set.  If the set is better than the previously known best set, the
        ' best set is replaced.  If all threads are done, gemsDone flag is set to true so that execution
        ' in the main thread will continue.
        Try
            SyncLock threadDoneLock
                numThreadsDone += 1
                If bestScore > bestGemScore Then
                    bestGemSet = bestSet.Clone
                    bestGemStats = bestStats.Clone
                    bestGemScore = bestScore
                End If
                If numThreadsDone = 4 Then
                    gemsDone = True
                End If
            End SyncLock
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Private Sub finishCalculate()
        ' The goal here is to establish an object of type FullSet containing item and
        ' object information to pass back to the parent object.  The item will represent
        ' the best found set from the run of CalcManager.
        Try
            ' Generic Variable Definitions.
            Dim bestFullSet As FullSet
            Dim loopvar As Integer, loopvar2 As Integer

            ' These values must be updated so that the progress checked from
            ' the parent object can be used to continue program execution.
            gemsChecked = gemsToCheck
            progGemsDone = True


            ' Instantiate the set and populate the item, gem, and enchant information along
            ' with total stats and several calculations involving score and final
            ' hit and expertise information. Enchants for non-dynamic slots are added
            ' at this time and stats are incremented.  Professions bonuses or specific enchants
            ' are also added at this time.
            bestFullSet = New FullSet(numPieces)
            For loopvar = 0 To numPieces - 1
                bestFullSet.items(loopvar) = v_items(trans(loopvar), bestGemSet.itemIDs(loopvar)).clone
            Next
            bestFullSet.metaGem = bestGemSet.metaGem
            For loopvar = 0 To numPieces - 1
                For loopvar2 = 0 To 2
                    If bestGemSet.gemIDs(loopvar, loopvar2) <> -1 Then
                        bestFullSet.gems(loopvar, loopvar2) = v_gems(bestGemSet.gemIDs(loopvar, loopvar2)).clone
                    Else
                        bestFullSet.gems(loopvar, loopvar2) = New Gem
                        bestFullSet.gems(loopvar, loopvar2).clear()
                    End If
                Next
            Next
            bestFullSet.totalScore = bestGemScore
            bestFullSet.totalStats = bestGemStats.Clone
            bestFullSet.headEnch = Enchants.head_ArcanumOfTorment
            bestFullSet.totalStats(stats.ap) += 50
            bestFullSet.totalStats(stats.crit) += 20
            bestFullSet.totalScore += ((50 * v_weights.ap) + (20 * v_weights.crit))
            If v_profs(0) = Professions.Inscription Or v_profs(1) = Professions.Inscription Then
                bestFullSet.shoulderEnch = Enchants.shoulder_MastersAxe
                bestFullSet.totalStats(stats.ap) += 120
                bestFullSet.totalStats(stats.crit) += 15
                bestFullSet.totalScore += ((120 * v_weights.ap) + (15 * v_weights.crit))
            Else
                bestFullSet.shoulderEnch = Enchants.shoulder_GreaterAxe
                bestFullSet.totalStats(stats.ap) += 40
                bestFullSet.totalStats(stats.crit) += 15
                bestFullSet.totalScore += ((40 * v_weights.ap) + (15 * v_weights.crit))
            End If
            If v_weights.agi * 22 > v_weights.haste * 23 Then
                If v_profs(0) = Professions.Tailoring Or v_profs(1) = Professions.Tailoring Then
                    If (400 * v_weights.ap) * 0.25 > v_weights.agi * 22 Then
                        bestFullSet.backEnch = Enchants.back_SwordguardEmbroidery
                        bestFullSet.totalScore += ((400 * v_weights.ap) * 0.25)
                    Else
                        bestFullSet.backEnch = Enchants.back_MajorAgility
                        bestFullSet.totalStats(stats.agi) += 22
                        bestFullSet.totalScore += (22 * v_weights.agi)
                    End If
                Else
                    bestFullSet.backEnch = Enchants.back_MajorAgility
                    bestFullSet.totalStats(stats.agi) += 22
                    bestFullSet.totalScore += (22 * v_weights.agi)
                End If
            Else
                If v_profs(0) = Professions.Tailoring Or v_profs(1) = Professions.Tailoring Then
                    If (400 * v_weights.ap) * 0.25 > v_weights.haste * 23 Then
                        bestFullSet.backEnch = Enchants.back_SwordguardEmbroidery
                        bestFullSet.totalScore += ((400 * v_weights.ap) * 0.25)
                    Else
                        bestFullSet.backEnch = Enchants.back_GreaterSpeed
                        bestFullSet.totalStats(stats.haste) += 23
                        bestFullSet.totalScore += (23 * v_weights.haste)
                    End If
                Else
                    bestFullSet.backEnch = Enchants.back_GreaterSpeed
                    bestFullSet.totalStats(stats.haste) += 23
                    bestFullSet.totalScore += (23 * v_weights.haste)
                End If
            End If
            Dim numJCGemsAdded As Integer = 0
            If v_profs(0) = Professions.Jewelcrafting Or v_profs(1) = Professions.Jewelcrafting Then
                For loopvar = 0 To numPieces - 1
                    For loopvar2 = 0 To 2
                        If v_weights.str > v_weights.arp Then
                            If bestFullSet.gems(loopvar, loopvar2).name = "Bold Cardinal Ruby" And numJCGemsAdded < 3 Then
                                bestFullSet.gems(loopvar, loopvar2).name = "Bold Dragon's Eye"
                                bestFullSet.gems(loopvar, loopvar2).jcgem = True
                                bestFullSet.gems(loopvar, loopvar2).str = 34
                                bestFullSet.totalStats(stats.str) += 14
                                bestFullSet.totalScore += (v_weights.str * 14)
                                numJCGemsAdded += 1
                            End If
                        Else
                            If bestFullSet.gems(loopvar, loopvar2).name = "Fractured Cardinal Ruby" And numJCGemsAdded < 3 Then
                                bestFullSet.gems(loopvar, loopvar2).name = "Fractured Dragon's Eye"
                                bestFullSet.gems(loopvar, loopvar2).jcgem = True
                                bestFullSet.gems(loopvar, loopvar2).arp = 34
                                bestFullSet.totalStats(stats.arp) += 14
                                bestFullSet.totalScore += (v_weights.arp * 14)
                                numJCGemsAdded += 1
                            End If
                        End If
                    Next
                Next
            End If
            bestFullSet.chestEnch = Enchants.chest_PowerfulStats
            bestFullSet.totalStats(stats.str) += 10
            bestFullSet.totalStats(stats.agi) += 10
            bestFullSet.totalScore += ((10 * v_weights.str) + (10 * v_weights.agi))
            bestFullSet.wristEnch = bestGemSet.wEnch
            bestFullSet.handEnch = bestGemSet.hEnch
            bestFullSet.legEnch = Enchants.leg_IcescaleArmor
            bestFullSet.totalStats(stats.ap) += 75
            bestFullSet.totalStats(stats.crit) += 22
            bestFullSet.totalScore += ((75 * v_weights.ap) + (22 * v_weights.crit))
            bestFullSet.footEnch = bestGemSet.fench
            If v_profs(0) = Professions.Enchanting Or v_profs(1) = Professions.Enchanting Then
                bestFullSet.finger1Ench = Enchants.finger_Assault
                bestFullSet.finger2Ench = Enchants.finger_Assault
                bestFullSet.totalStats(stats.ap) += 80
                bestFullSet.totalScore += (80 * v_weights.ap)
            Else
                bestFullSet.finger1Ench = Enchants.none
                bestFullSet.finger2Ench = Enchants.none
            End If
            If v_profs(0) = Professions.Skinning Or v_profs(1) = Professions.Skinning Then
                bestFullSet.totalStats(stats.crit) += 40
                bestFullSet.totalScore += (40 * v_weights.crit)
            End If
            If v_profs(0) = Professions.Alchemy Or v_profs(1) = Professions.Alchemy Then
                bestFullSet.totalScore += (80 * v_weights.ap)
            End If
            bestFullSet.meleeHitPercent = 100 - (System.Math.Floor((c_HitCap - bestFullSet.totalStats(stats.hit)) / c_MeleeHitperPercent) + 1)
            If bestFullSet.meleeHitPercent > 100 Then bestFullSet.meleeHitPercent = 100
            bestFullSet.spellHitPercent = 100 - (System.Math.Floor((c_SpellCap - bestFullSet.totalStats(stats.hit)) / c_SpellHitperPercent) + 1)
            If bestFullSet.spellHitPercent > 100 Then bestFullSet.spellHitPercent = 100
            bestFullSet.Expertise = System.Math.Floor(bestFullSet.totalStats(stats.exp) / c_ExpperExpertise) + v_expTalents
            bestFullSet.dodgeChance = 6.5 - (0.25 * (bestFullSet.totalStats(stats.exp) / c_ExpperExpertise)) - (0.25 * v_expTalents)
            If bestFullSet.dodgeChance < 0 Then bestFullSet.dodgeChance = 0
            bestFullSet.totalStats(stats.exp) = System.Math.Floor(bestFullSet.totalStats(stats.exp) - v_expBonusRating(bestFullSet.items(slotIdx.weapon1).weptype))

            ' Raise the event that shows calculation is finished.  The event
            ' also passes the best set back to the calling object.
            RaiseEvent CalculateFinish(bestFullSet)
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try

    End Sub

    Private Function trans(ByVal slotindex As Integer) As Integer
        ' This function is used to transform a slot index value to an item type
        ' value.  Slot index range from 0 to 15 and differ from item types because
        ' of the use of two rings, trinkets, and weapons.  Item types only range from
        ' 0 to 12.
        Try
            If slotindex > slotIdx.foot Then
                If slotindex = slotIdx.finger1 Or slotindex = slotIdx.finger2 Then
                    trans = 10
                ElseIf slotindex = slotIdx.trinket1 Or slotindex = slotIdx.trinket2 Then
                    trans = 11
                ElseIf slotindex = slotIdx.weapon1 Or slotindex = slotIdx.weapon2 Then
                    trans = 12
                End If
            Else
                trans = slotindex
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try

    End Function

    Private Sub gearFinder_OneSetChecked(ByVal numSets As Double) Handles gearFinder_0.SetChecked, gearFinder_1.SetChecked, gearFinder_2.SetChecked, gearFinder_3.SetChecked
        ' This handles the event from the gear candidate threads that is called to increase the
        ' total progress reported to the parent object from the public variables in CalcManager.
        Try
            SyncLock iterateLock
                candidatesChecked += numSets
            End SyncLock
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Private Sub gemFinder_oneSetChecked() Handles gemFinder_0.oneSetChecked, gemFinder_1.oneSetChecked, gemFinder_2.oneSetChecked, gemFinder_3.oneSetChecked
        ' This handles the event from the gem threads that is called to increase the
        ' total progress reported to the parent object from the public variables in CalcManager.
        Try
            SyncLock iterateLock
                gemsChecked += 1
            End SyncLock
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub
End Class
